package com.example.office.util.pay;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * @Author jujh
 * @Date 2018/8/2
 */
public class SignatureUtils {

    /**
     * 接口生成签名示例 。测试地址（车品牌查询）：https://open.kartor.cn/res/car/brand/all
     * ?appId=8f11044eca4048aab1c8935a04d60e8b&timestamp=20161011183737&nonce=
     * SsFXTYc0 &signature=2BslWzlzEnWUq3mE%2BmN2PAdUlR0%3D
     */
    public static void main(String[] str) throws Exception {
        // map里面的key和value对应http请求里面的key和value
        Map map = new HashMap<>();
        map.put("timestamp", "20161011183737");
        map.put("appId", "8f11044eca4048aab1c8935a04d60e8b");
        map.put("nonce", "SsFXTYc0");
        // 生成签名的源串
        String urlParam = toUrlParam(sort(map));
        System.out.println("源串：" + urlParam);
        // 计算签名
        String sig = generateSignature(urlParam, "f137f3dd5f3c483c9f8378366b08963f");
        System.out.println("签名：" + sig); // 结果2BslWzlzEnWUq3mE+mN2PAdUlR0=
        // ecode签名, 结果为2BslWzlzEnWUq3mE%2BmN2PAdUlR0%3D
        System.out.println("签名编码：" + URLEncoder.encode(sig, "UTF-8"));
    }

    public static String sign(Map<String, String> map, String key) throws Exception {
        // 生成签名的源串
        String urlParam = toUrlParam(sort(map));
        // 计算签名
        String sig = generateSignature(urlParam, key);
        return URLEncoder.encode(sig, "UTF-8");
    }

    /**
     * 将map里面的key和value拼接成签名的源串
     *
     * @param sortedMap 经过排序的map
     * @return 源串
     */
    private static String toUrlParam(TreeMap sortedMap)
            throws UnsupportedEncodingException {
        String urlParam = "";
        for (Object mapEntry : sortedMap.entrySet()) {
            Map.Entry entry = (Map.Entry) mapEntry;
            String key = entry.getKey() + "";
            String value = entry.getValue() + "";
            // signature 参数不作为签名计算的源串； key或者value为空，也不作为计算签名的源串
            if (key == null || value == null || "signature".equals(key)) {
                continue;
            }
            // 对value需要encode
            String encodedValue = URLEncoder.encode(value, "UTF-8");
            urlParam += (key + "=" + encodedValue + "&");
        }
        // 删除最后一个&符号
        urlParam = urlParam.substring(0, urlParam.length() - 1);
        return urlParam;
    }

    /**
     * 对参数map排序
     *
     * @param map 包含http参数的map
     * @return 排序后的map
     */
    private static TreeMap sort(Map map) {
        if (map == null || map.isEmpty()) {
            throw new NullPointerException("The parameter map must not be null.");
        }
        TreeMap sortedMap = new TreeMap<>();
        // tree map会自动排序
        sortedMap.putAll(map);
        return sortedMap;
    }

    /**
     * 生成签名数据
     *
     * @param data 待加密的数据
     * @param key  加密使用的key
     */
    public static String generateSignature(String data, String key) throws Exception {
        String stringSignTemp = data + "&key=" + key; //注：key为商户平台设置的密钥key
        String sign = WXPayUtil.MD5(stringSignTemp).toUpperCase();
        sign = WXPayUtil.HMACSHA256(data, key).toUpperCase();
        return sign;
    }

    /**
     * 对 data 以key 进行HmacSHA1加密，然后生成base64
     *
     * @param data 签名源串的字节数组
     * @param key  签名key的字节数组
     * @return 签名串
     */
    private static String generateSignature(byte[] data, byte[] key)
            throws InvalidKeyException, NoSuchAlgorithmException {
        SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(data);
        // 用base64进行编码
        return getBase64Signature(rawHmac);
    }

    /**
     * 将原始二进制数据数据，进行base64编码
     *
     * @return 签名串
     */
    private static String getBase64Signature(byte[] bytes) {
        byte[] base64Text = Base64.encodeBase64(bytes);
        return new String(base64Text);
    }

}
